package bma.common.json.xdom;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.ObjectNode;

import bma.common.json.JsonUtil;
import bma.common.langutil.core.ExceptionUtil;
import bma.common.langutil.core.Function;
import bma.common.langutil.core.IteratorUtil;
import bma.common.langutil.xdom.Xdom;
import bma.common.langutil.xdom.XdomType;
import bma.common.langutil.xdom.core.XdomBoolean;
import bma.common.langutil.xdom.core.XdomNULL;
import bma.common.langutil.xdom.core.XdomNumber;
import bma.common.langutil.xdom.core.XdomString;

public class XdomJson {

	public XdomJson() {
		super();
	}

	public Xdom bind(JsonNode obj) {
		if (obj == null)
			return XdomNULL.NULL;
		if (obj.isNull())
			return XdomNULL.NULL;
		if (obj.isTextual()) {
			return new XdomString(obj.getTextValue());
		}
		if (obj.isNumber()) {
			return new XdomNumber(obj.getNumberValue());
		}
		if (obj.isBoolean()) {
			return new XdomBoolean(obj.getBooleanValue());
		}
		if (obj.isArray()) {
			return new XList(obj);
		}
		if (obj.isObject()) {
			return new XMap(obj);
		}
		return XdomNULL.NULL;
	}

	class XList extends Xdom {
		private JsonNode value;

		public XList(JsonNode v) {
			super();
			value = v;
		}

		@Override
		public XdomType type() {
			return XdomType.ARRAY;
		}

		@Override
		public int size() {
			return value.size();
		}

		@Override
		public Xdom get(int index) {
			if (!has(index)) {
				return XdomNULL.NULL;
			}
			return bind(value.get(index));
		}

		@Override
		public Iterator<Xdom> arrayIterator() {
			Iterator<JsonNode> it = value.iterator();
			return IteratorUtil.wrap(it, new Function<JsonNode, Xdom>() {
				@Override
				public Xdom apply(JsonNode input) {
					return bind(input);
				}
			});
		}
	}

	class XMap extends Xdom {

		private JsonNode value;

		public XMap(JsonNode value) {
			super();
			this.value = value;
		}

		@Override
		public XdomType type() {
			return XdomType.MAP;
		}

		@Override
		public String xtype() {
			JsonNode v = value.get("_xtype");
			return v == null ? null : v.asText();
		}

		@Override
		public int size() {
			return value.size();
		}

		@Override
		public boolean has(String key) {
			return value.has(key);
		}

		@Override
		public Xdom get(String fieldName) {
			return bind(value.get(fieldName));
		}

		@SuppressWarnings("unchecked")
		@Override
		public Iterator<String> mapNames() {
			Iterator<String> it = value.getFieldNames();
			if (value.has("_xtype")) {
				List<String> r = new ArrayList<String>();
				while (it.hasNext()) {
					String s = it.next();
					if (s.equals("_xtype"))
						continue;
					r.add(s);
				}
				return r.iterator();
			}
			return value.getFieldNames();
		}
	}

	public static JsonNode fromXdom(Xdom dom) {
		return fromXdom(JsonUtil.getDefaultMapper(), dom);
	}

	public static JsonNode fromXdom(ObjectMapper mapper, Xdom dom) {
		switch (dom.type()) {
		case ARRAY: {
				ArrayNode r = mapper.createArrayNode();
				Iterator<Xdom> it = dom.arrayIterator();
				while(it.hasNext()) {
					Xdom v = it.next();
					JsonNode node = fromXdom(mapper, v);
					r.add(node);
				}
				return r;
			}
		case BOOLEAN:
			return mapper.getNodeFactory().booleanNode(dom.getBooleanValue());
		case MAP: {
				ObjectNode r = mapper.createObjectNode();
				Iterator<String> it = dom.mapNames();
				while(it.hasNext()) {
					String n = it.next();
					Xdom v = dom.get(n);
					JsonNode node = fromXdom(mapper, v);
					r.put(n, node);
				}
				return r;
			}
		case NULL:
			return mapper.getNodeFactory().nullNode();
		case NUMBER:
			try {
				return mapper.readTree(dom.getNumberValue().toString());
			} catch (Exception e) {
				throw ExceptionUtil.throwRuntime(e);
			}
		case STRING:
			return mapper.getNodeFactory().textNode(dom.getStringValue());
		}
		return null;
	}
}
